# Try to play the most interesting episode.
# Interestingness is a combination of:
# - Time since last time this feed was recommended
# - Average time from download to listen
# - p(listen)^2

import index
import time
import random
import math

day = 86400.0
# These are some wild ass guesses. Collect some data then try to fit a model.
p_listen_sq_weight = 2.0

# Probability of recommending a random feed. Allows really unpopular feeds to
# come back.
p_recommend_random = 0.1

def sgn(n):
  """Returns:
  n < 0: -1
  n == 0: 0
  n > 0: 1"""
  return cmp(n, 0)

def RecommendRandom():
  feed_ids = list(index.FeedIDs())
  while feed_ids:
    i = int(random.random() * len(feed_ids))
    try:
      feed_id = feed_ids[i]
      del feed_ids[i]
      feed = index.GetFeed(feed_id)
    except KeyError:
      continue
    if feed.HasUnlistenedEpisodes():
      return feed
  return None

def Recommend():
  global p_recommend_random
  if random.random() < p_recommend_random:
    print "Making a random recommendation"
    feed = RecommendRandom()
  else:
    print "%20s  %12s  %12s  %14s  %12s  %12s" % (
        "feed", "last_choose", "points", "plisten",
        "points", "score")
    best_score = None
    best_feed = None
    for feed_id in index.FeedIDs():
      try:
        feed = index.GetFeed(feed_id)
      except KeyError:
        continue
      if feed.HasUnlistenedEpisodes():
        score = ScoreFeed(feed)
        if best_feed is None or score > best_score:
          best_score = score
          best_feed = feed

    feed = best_feed
  if feed:
    feed.SetChosen()
  return feed

def ScoreFeed(feed):
  global p_listen_sq_weight

  stats = feed.stats()
  #print feed,stats
  last_choose_time = stats.get("last_choose_time", None)
  p_listen_list = stats.get("p_listen", [])
  if not last_choose_time:
    # Have never listened.
    print "%20s  %12s  %12s  %12s  %12s  %12s [never listened]" % (feed.title(),
        None, None, None, None, None, None, 1.0)
    return 10.0

  if not p_listen_list:
    p_listen = 0
    raw_p_listen = 0
    p_listen_sgn = 0
  else:
    sum = reduce(lambda a, b: a + b, p_listen_list)
    p_listen = float(sum) / len(p_listen_list)
    raw_p_listen = p_listen
    p_listen -= 0.5
    p_listen_sgn = sgn(p_listen)

  age_sec = time.time() - last_choose_time
  age_hours = max(0.01, age_sec / 3600.0 - 1)

  age_points = (age_hours / 48.0) - (1 / (age_hours))
  pref_points = (p_listen_sq_weight * p_listen**2 * p_listen_sgn *
                 len(p_listen_list))
  score = age_points + pref_points

  # The score is computed as the sum of:
  #   time since last chosen, according to this schedule:
  #     Less than 1 hour ago: -100
  #     2 hours ago:   -.97
  #     1 day ago:      .43
  #     2 days ago:     .95
  #     3 days ago:    1.46
  #     7 days ago:    3.45
  #     ...and continuing to increase at .5 points per day.
  #
  #       * the cap is there so that your least favorite feeds will never
  #         play if your most favorite are available.
  #
  #   probability of listening, according to this schedule:
  #
  #     Num chances->   0      1      2      3      4      5      6      7
  #  Num listen  0   0.00  -0.50  -1.00  -1.50  -2.00  -2.50  -3.00  -3.50
  #      |       1          0.50   0.00  -0.17  -0.50  -0.90  -1.33  -1.79
  #      V       2                 1.00   0.17   0.00  -0.10  -0.33  -0.64
  #              3                        1.50   0.50   0.10   0.00  -0.07
  #              4                               2.00   0.90   0.33   0.07
  #              5                                      2.50   1.33   0.64
  #              6                                             3.00   1.79
  #              7                                                    3.50
  #
  # TODO: Add bonus points to infrequently updated podcasts, penalty for
  #       frequently updated.
  print "%20s   %02d:%02d:%02d           %-12f   %0.2f (of %d)    %-12f   %-12f" % (
      feed.title()[:20],
      age_sec / 3600, (age_sec / 60) % 60, age_sec % 60,
      age_points,
      raw_p_listen, len(p_listen_list),
      pref_points,
      score)
  return score
